/*
 * Sales Data Controller Project.
 * Copyright (C) 2011 FedICT.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version
 * 3.0 as published by the Free Software Foundation.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, see 
 * http://www.gnu.org/licenses/.
 */

package be.fedict.sdc.vsc.admin;

import java.io.IOException;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPublicKeySpec;

import javax.smartcardio.CardChannel;
import javax.smartcardio.CardException;
import javax.smartcardio.CommandAPDU;
import javax.smartcardio.ResponseAPDU;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import be.fedict.sdc.vsc.VATSigningCard;
import be.fedict.sdc.vsc.tlv.TLVContext;
import be.fedict.sdc.vsc.tlv.TLVMarshaller;

public class VATSigningCardAdmin extends VATSigningCard {

	private static final Log LOG = LogFactory.getLog(VATSigningCardAdmin.class);

	private final TLVMarshaller<PersonalizationData> personalizationDataMarshaller;

	public VATSigningCardAdmin(CardChannel cardChannel) throws CardException {
		super(cardChannel);

		TLVContext<PersonalizationData> tlvContext = TLVContext
				.newInstance(PersonalizationData.class);
		this.personalizationDataMarshaller = tlvContext.getMarshaller();
	}

	public void generateKeypair() throws CardException {
		LOG.debug("generate keypair");
		CommandAPDU generateKeypairApdu = new CommandAPDU(0x00, 0x46, 0x00,
				0x00);
		long t0 = System.currentTimeMillis();
		ResponseAPDU responseApdu = this.cardChannel
				.transmit(generateKeypairApdu);
		long t1 = System.currentTimeMillis();
		int statusWord = responseApdu.getSW();
		if (0x9000 != statusWord) {
			throw new CardException("not a VAT Signing Card ");
		}
		LOG.debug("done in: " + (t1 - t0) + " ms.");
	}

	public RSAPublicKey getPublicKey() throws CardException {
		CommandAPDU getKeyApdu = new CommandAPDU(0x80, 0xE2, 0x00, 0x00,
				5 + 8 + 128);
		ResponseAPDU responseApdu = cardChannel.transmit(getKeyApdu);
		int statusWord = responseApdu.getSW();
		if (0x9000 != statusWord) {
			throw new CardException("status word: "
					+ Integer.toHexString(statusWord));
		}
		byte[] data = responseApdu.getData();
		if (5 + 8 + 128 != data.length) {
			throw new CardException("invalid data length: " + data.length);
		}

		if (0x02 != data[0]) {
			throw new CardException("exponent tag error");
		}
		if (0x08 != data[1]) {
			throw new CardException("exponent length error");
		}
		byte[] exponent = new byte[8];
		System.arraycopy(data, 2, exponent, 0, 8); // value

		if (0x03 != data[10]) {
			throw new CardException("modulus tag error");
		}
		if ((byte) 0x81 != data[11]) {
			throw new CardException("modulus length error");
		}
		if ((byte) 0x80 != data[12]) {
			throw new CardException("modulus length error");
		}
		byte[] modulus = new byte[128];
		System.arraycopy(data, 13, modulus, 0, 128); // value

		KeyFactory keyFactory;
		try {
			keyFactory = KeyFactory.getInstance("RSA");
		} catch (NoSuchAlgorithmException e) {
			throw new CardException("no RSA supported");
		}
		RSAPublicKeySpec rsaPublicKeySpec = new RSAPublicKeySpec(
				new BigInteger(1, modulus), new BigInteger(1, exponent));
		RSAPublicKey rsaPublicKey;
		try {
			rsaPublicKey = (RSAPublicKey) keyFactory
					.generatePublic(rsaPublicKeySpec);
		} catch (InvalidKeySpecException e) {
			throw new CardException("invalid key specs: " + e.getMessage());
		}
		return rsaPublicKey;
	}

	public void personalize(PersonalizationData personalizationData)
			throws IOException, CardException {
		byte[] encodedPersonalizationData = this.personalizationDataMarshaller
				.marshall(personalizationData);
		CommandAPDU putDataApdu = new CommandAPDU(0x00, 0xDA, 0x00, 0x51,
				encodedPersonalizationData);
		ResponseAPDU responseApdu = cardChannel.transmit(putDataApdu);
		int statusWord = responseApdu.getSW();
		if (0x9000 != statusWord) {
			throw new CardException("status word: "
					+ Integer.toHexString(statusWord));
		}
	}
}
